linux 启动过程

说起 linux 的启动,用“牵一发动全身”来形容在形象不过了。虽然按下开关后,屏幕仍然是黑黑的,但是实际上程序已经在策马奔腾。

基础部分


aliases:

  • cpp段说明
    tags:
    dg-created: 2023-03-28T16:29:56+08:00
    dg-updated: 2023-03-28T16:30:13+08:00

段说明

  • text 段,代码段,通常指用来存储程序代码的一块内存区域。区域的大小在程序运行前就已经确定。
  • data 段,数据段,通常指用来存储程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。
  • bss 段,通常指用来存储程序中未初始化的全局变量和静态变量的一块区域。bss 段属于静态内存分配。
  • init 段,linux 定义的一种在初始化过程中才会用到的段,一旦初始化完成,这段的内存就会被释放掉。

地址

在 linux 中,存在以下地址

  • 加载地址
  • 运行地址
  • 链接地址
  • 虚拟地址
  • 物理地址

加载地址

程序中指令和数据加载到 RAM 上的地址

运行地址

CPU 执行一条指令时的执行地址,也就是 PC 寄存器的值

链接地址

链接过程中链接器为指令和变量分配的地址

物理地址

CPU 在访问内存时,要给出内存的地址。所有的内存单元构成一个一维的线性空间,每一个内存单元在这个空间里面都有唯一的一个地址,我们将这个唯一的地址称为物理地址。

在 8086 CPU(16 位)中,能够一次处理、传输、暂时存储的信息的大小为 16 位。在 CPU 访问内存时,其地址在送上地址总线之前,必须由 CPU 处理。

8086CPU 有 20 根地址总线,其最大寻址范围是 1MB。如果直接由 CPU 发出,那么地址只有 16 位,与事实显然不符。那么,是怎么解决的呢?

引入偏移地址(段内偏移),

=<<4+

dbbc02ecd34e57ad1f58dc6f5638bbd5.gif

虚拟地址/逻辑地址

在早期的 CPU 中,没有虚拟地址的概念,程序是直接运行在物理地址中的。

随着科技的发展,将程序直接跑在物理地址下越来越不方便:

  • 进程空间地址不隔离
  • 内存使用效率低
  • 程序运行的地址不确定

为了解决这个问题,人们想了一个 中间层(MMU),利用一种间接的方法去访问内存。

graph LR

 进程A--> MMU --> 物理内存
 进程B--> MMU

地址之间的关系

  • 地址解释
    • 加载地址:程序中指令和数据加载到 RAM 上的地址
    • 运行地址:CPU 执行一条指令时的执行地址,也就是 PC 寄存器的值。
    • 链接地址:链接过程中链接器为指令和变量分配的地址
  • 地址之间的关系
    注意运行并不一定完全和链接地址相同,也不一定完全和加载地址相同
    • 如果没有打开 MMU,并且使用的是 位置相关设计,那么加载地址、运行地址、链接地址需要保持一致。比如 u-boot
    • 打开 MMU 之前,如果使用的是位置无关设计,那么运行地址和加载地址是一致的。比如 kernel 在打开 MMU 之前,使用的是 位置无关设计 位置无关设计,其运行地址与加载地址一致。
    • 如果打开了 MMU,那么运行地址和链接地址相同。硬件会自动根据运行地址计算并寻址到对应的加载地址上。
  • 举例说明
    以 s5pv210 为例
    • u-boot 阶段没有打开 MMU,并且使用的是位置相关设计,所以加载地址和链接地址都需要是相同的,也就是 0x23E00000
    • kernel 启动过程中,在打开 MMU 之前,使用的是位置无关设计,
      内核镜像加载地址是 0x20008000,链接地址是 0xc0008000,运行地址是 0x20008000
    • 打开 MMU 之后,
      内核镜像加载地址是 0x20008000,链接地址是 0xc0008000,运行地址是 0xc0008000

Linux 操作系统启动流程

在 x86 下,其启动流程如下,arm 平台与 x86 启动不一致。

111242100.png

第一阶段 硬件引导启动

第一阶段主要是硬件引导启动,

x86 平台

CPU 运行在实模式中,其物理地址:CS16+EIPCS 段地址,EIP 段内偏移地址

当你按下 power 键的那一刻,CS 寄存器被复位为 0xFFFF,EIP 寄存器被复位为 0,CPU 访问地址为:0xFFFF0,在实模式下,此时还没有突破 A20 线,其可访问的最大地址也就是 0xFFFFF(1Mb),此处只有 16 字节,太小以至于无法存储 bios 代码,所以此处一般为一个跳转指令,跳转到真正的 bios 处,然后在执行 bios 的代码。

在 bios 中,首先会 POST:Power On Self Test, 上电自检,之后对计算机各部件进行初始化,如有错误则报警提示。

下一步在外部存储器中寻找操作系统,找到第一个可启动设备。

Arm 侧

当 Arm 处理器上电后,首先会运行 BootRom,BootRom 会初始化处理器并加载 Bootloader。

第二阶段 bootloader

是一个小的裸机程序,它的的主要任务是加载内核镜像并将其解压到内存中。常见的 bootloader 有 u-boot、redboot 等。

后面分析 u-boot

TODO 待分析

bootm Legacy-uImage加载地址 ramdisk加载地址 dtb加载地址

在 u-boot 中,使用 bootm 命令将 kernel、dtb 载入,并搬运至指定地址(此指定地址受限于 芯片手册中的 SDRAM 的首地址,一定要在手册中的 SDRAM 地址限定范围内)

在 boot_jump_linux 中,配置内核启动之前对寄存器的要求:R0 为 0,R1 为 machid,R2 为 atags 或 设备树地址。

接着就是 kernel_entry(0, machid, r2) 也就是执行内核 entry point 处的代码,换句话说,就跳转到了 kernel 中。

第三阶段 kernel 初始化阶段

当前内核版本 5.17

进入 kernel 的限制条件

在 head.S 中主要功能如下,按照从前往后的顺序

init/main.cstart_kernel 中,主要的功能如下

第四阶段 Sys V/Systemd 阶段

第五阶段 启动完成